Etant un amateur de basket-ball mais aussi un amateur de machine-learning, je vous propose de vous présenter les travaux que j’ai pu réaliser pour la compétition kaggle : Kobe Bryant Shot Selection. Le but de cette compétition est de prédire la probabilité de réussite aux tirs de Kobe Bryant. Pour ce faire, nous disposons de l’ensemble de ses shots de sa carrière. Ces derniers sont représentés suivant un grand nombre de variables que nous verrons par la suite. Seule une variable est importante à ce stage : ‘shot_made_flag’ qui nous dit si le shot a été réussi ou non. Elle a pour valeur 0 ou 1 et représente une probabilité. Pour mettre en place la compétition, kaggle a retiré l’information ‘shot_made_flag’ de 5000 tirs au hasard. A nous d’estimer cette probabilité.
shots = read.csv("data/data.csv", stringsAsFactors = T)
shots$shot_made_flag <- as.factor(shots$shot_made_flag)
train = shots[!is.na(shots$shot_made_flag),]
test = shots[is.na(shots$shot_made_flag),]
Une fois le chargement des données effectuées dans la variable shots, on peut distinguer deux jeux de données : - train : les shots où l’on connaît la réussite du tir, - test : les shots où l’on ne connaît pas la réussite du tir.
names(shots)
## [1] "action_type" "combined_shot_type" "game_event_id"
## [4] "game_id" "lat" "loc_x"
## [7] "loc_y" "lon" "minutes_remaining"
## [10] "period" "playoffs" "season"
## [13] "seconds_remaining" "shot_distance" "shot_made_flag"
## [16] "shot_type" "shot_zone_area" "shot_zone_basic"
## [19] "shot_zone_range" "team_id" "team_name"
## [22] "game_date" "matchup" "opponent"
## [25] "shot_id"
summary(shots)
## action_type combined_shot_type game_event_id
## Jump Shot :18880 Bank Shot: 141 Min. : 2.0
## Layup Shot : 2567 Dunk : 1286 1st Qu.:110.0
## Driving Layup Shot : 1978 Hook Shot: 153 Median :253.0
## Turnaround Jump Shot: 1057 Jump Shot:23485 Mean :249.2
## Fadeaway Jump Shot : 1048 Layup : 5448 3rd Qu.:368.0
## Running Jump Shot : 926 Tip Shot : 184 Max. :659.0
## (Other) : 4241
## game_id lat loc_x loc_y
## Min. :20000012 Min. :33.25 Min. :-250.000 Min. :-44.00
## 1st Qu.:20500077 1st Qu.:33.88 1st Qu.: -68.000 1st Qu.: 4.00
## Median :20900354 Median :33.97 Median : 0.000 Median : 74.00
## Mean :24764066 Mean :33.95 Mean : 7.111 Mean : 91.11
## 3rd Qu.:29600474 3rd Qu.:34.04 3rd Qu.: 95.000 3rd Qu.:160.00
## Max. :49900088 Max. :34.09 Max. : 248.000 Max. :791.00
##
## lon minutes_remaining period playoffs
## Min. :-118.5 Min. : 0.000 Min. :1.000 Min. :0.0000
## 1st Qu.:-118.3 1st Qu.: 2.000 1st Qu.:1.000 1st Qu.:0.0000
## Median :-118.3 Median : 5.000 Median :3.000 Median :0.0000
## Mean :-118.3 Mean : 4.886 Mean :2.519 Mean :0.1466
## 3rd Qu.:-118.2 3rd Qu.: 8.000 3rd Qu.:3.000 3rd Qu.:0.0000
## Max. :-118.0 Max. :11.000 Max. :7.000 Max. :1.0000
##
## season seconds_remaining shot_distance shot_made_flag
## 2005-06: 2318 Min. : 0.00 Min. : 0.00 0 :14232
## 2008-09: 2242 1st Qu.:13.00 1st Qu.: 5.00 1 :11465
## 2002-03: 2241 Median :28.00 Median :15.00 NA's: 5000
## 2007-08: 2153 Mean :28.37 Mean :13.44
## 2009-10: 2080 3rd Qu.:43.00 3rd Qu.:21.00
## 2001-02: 2028 Max. :59.00 Max. :79.00
## (Other):17635
## shot_type shot_zone_area
## 2PT Field Goal:24271 Back Court(BC) : 83
## 3PT Field Goal: 6426 Center(C) :13455
## Left Side Center(LC) : 4044
## Left Side(L) : 3751
## Right Side Center(RC): 4776
## Right Side(R) : 4588
##
## shot_zone_basic shot_zone_range team_id
## Above the Break 3 : 5620 16-24 ft. :8315 Min. :1.611e+09
## Backcourt : 71 24+ ft. :6275 1st Qu.:1.611e+09
## In The Paint (Non-RA): 4578 8-16 ft. :6626 Median :1.611e+09
## Left Corner 3 : 280 Back Court Shot: 83 Mean :1.611e+09
## Mid-Range :12625 Less Than 8 ft.:9398 3rd Qu.:1.611e+09
## Restricted Area : 7136 Max. :1.611e+09
## Right Corner 3 : 387
## team_name game_date matchup
## Los Angeles Lakers:30697 2016-04-13: 50 LAL @ SAS : 1020
## 2002-11-07: 47 LAL vs. SAS: 936
## 2006-01-22: 46 LAL @ SAC : 889
## 2006-12-29: 45 LAL vs. HOU: 878
## 2007-03-30: 44 LAL @ DEN : 873
## 2008-01-14: 44 LAL @ PHX : 859
## (Other) :30421 (Other) :25242
## opponent shot_id
## SAS : 1978 Min. : 1
## PHX : 1781 1st Qu.: 7675
## HOU : 1666 Median :15349
## SAC : 1643 Mean :15349
## DEN : 1642 3rd Qu.:23023
## POR : 1539 Max. :30697
## (Other):20448
str(shots)
## 'data.frame': 30697 obs. of 25 variables:
## $ action_type : Factor w/ 57 levels "Alley Oop Dunk Shot",..: 27 27 27 27 6 27 28 27 27 42 ...
## $ combined_shot_type: Factor w/ 6 levels "Bank Shot","Dunk",..: 4 4 4 4 2 4 5 4 4 4 ...
## $ game_event_id : int 10 12 35 43 155 244 251 254 265 294 ...
## $ game_id : int 20000012 20000012 20000012 20000012 20000012 20000012 20000012 20000012 20000012 20000012 ...
## $ lat : num 34 34 33.9 33.9 34 ...
## $ loc_x : int 167 -157 -101 138 0 -145 0 1 -65 -33 ...
## $ loc_y : int 72 0 135 175 0 -11 0 28 108 125 ...
## $ lon : num -118 -118 -118 -118 -118 ...
## $ minutes_remaining : int 10 10 7 6 6 9 8 8 6 3 ...
## $ period : int 1 1 1 1 2 3 3 3 3 3 ...
## $ playoffs : int 0 0 0 0 0 0 0 0 0 0 ...
## $ season : Factor w/ 20 levels "1996-97","1997-98",..: 5 5 5 5 5 5 5 5 5 5 ...
## $ seconds_remaining : int 27 22 45 52 19 32 52 5 12 36 ...
## $ shot_distance : int 18 15 16 22 0 14 0 2 12 12 ...
## $ shot_made_flag : Factor w/ 2 levels "0","1": NA 1 2 1 2 1 2 NA 2 1 ...
## $ shot_type : Factor w/ 2 levels "2PT Field Goal",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ shot_zone_area : Factor w/ 6 levels "Back Court(BC)",..: 6 4 3 5 2 4 2 2 4 2 ...
## $ shot_zone_basic : Factor w/ 7 levels "Above the Break 3",..: 5 5 5 5 6 5 6 6 3 3 ...
## $ shot_zone_range : Factor w/ 5 levels "16-24 ft.","24+ ft.",..: 1 3 1 1 5 3 5 5 3 3 ...
## $ team_id : int 1610612747 1610612747 1610612747 1610612747 1610612747 1610612747 1610612747 1610612747 1610612747 1610612747 ...
## $ team_name : Factor w/ 1 level "Los Angeles Lakers": 1 1 1 1 1 1 1 1 1 1 ...
## $ game_date : Factor w/ 1559 levels "1996-11-03","1996-11-05",..: 311 311 311 311 311 311 311 311 311 311 ...
## $ matchup : Factor w/ 74 levels "LAL @ ATL","LAL @ BKN",..: 29 29 29 29 29 29 29 29 29 29 ...
## $ opponent : Factor w/ 33 levels "ATL","BKN","BOS",..: 26 26 26 26 26 26 26 26 26 26 ...
## $ shot_id : int 1 2 3 4 5 6 7 8 9 10 ...
Ces fonctions nous permettent de mieux connaitre notre jeu de données. On voit que l’on a la position du tir sur le terrain, s’il s’agit d’un tir à 2 points, à 3 points, l’équipe adverse, le type de tir, etc. Nous reviendrons sur certaines de ces variables plus tard. Une chose remarquable à noter pour une étude statistiques est que il n’y a pas de donnée manquante.
Pour mieux appréhender nos données, une bonne solution est de les visualiser. Pour ce faire, nous définissons une fonction qui permet de représenter une variable en fonction de la position du tir sur le terrain.
courtplot <- function(feat) {
feat <- substitute(feat)
train %>%
ggplot(aes(x = lon, y = lat)) +
geom_point(aes_q(color = feat), alpha = 0.7, size = 3) +
ylim(c(33.7, 34.0883)) +
theme_void() +
ggtitle(paste(feat))
}
Est-ce qu’il existe un lien fort entre la position du tir et sa réussite ? Voyons ça :
courtplot(shot_made_flag)
## Warning: Removed 105 rows containing missing values (geom_point).
Nous obtenons une jolie représentation des tirs! On voit que Kobe Bryant a pris des tirs à peu prés partout sur un demi-terrain. On peut également voir une proéminence de la couleur rouge, qui indique un tir manqué. On peut noté aussi que Kobe a pris quelques “big deeper” et qu’il a y eu plus d’échec que de réussite.
On dispose d’une variable zone_shot_area. On peut aisément la représenter :
courtplot(shot_zone_area)
## Warning: Removed 105 rows containing missing values (geom_point).
On voit que nous n’avons que 6 zones, ce qui est peu. Cinq zones englobent des tirs à 2 et 3 points et la zone “Back Court” n’apparait pas sur le graphique. Les tirs’Back court’ sont les tirs pris dans la zone adverse.
prop.table(table(train$shot_made_flag, train$shot_zone_area), 2)
##
## Back Court(BC) Center(C) Left Side Center(LC) Left Side(L)
## 0 0.98611111 0.47444415 0.63882283 0.60312899
## 1 0.01388889 0.52555585 0.36117717 0.39687101
##
## Right Side Center(RC) Right Side(R)
## 0 0.61743281 0.59834154
## 1 0.38256719 0.40165846
98% de manqués pour les tirs back court!
Il existe aussi une variable shot_zone_basic :
courtplot(shot_zone_basic)
## Warning: Removed 105 rows containing missing values (geom_point).
On voit le même problème que précédemment, la zone ‘Backcourt’ n’est pas représentée et certaines zones sont très larges.
prop.table(table(train$shot_made_flag, train$shot_zone_basic), 2)
##
## Above the Break 3 Backcourt In The Paint (Non-RA) Left Corner 3
## 0 0.67076271 0.98333333 0.54561856 0.62916667
## 1 0.32923729 0.01666667 0.45438144 0.37083333
##
## Mid-Range Restricted Area Right Corner 3
## 0 0.59371439 0.38199595 0.66066066
## 1 0.40628561 0.61800405 0.33933934
62% de réussite dans la zone ‘Restricted Area’. En fouillant sur le site http://stats.nba.com/, on peut trouver une représentation du terrain découpé en 14 zones.
On peut alors introduire une nouvelle variable pour avoir un découpage du terrain comme sur l’image. En utilisant les positions du tir, si le tir est à 2 ou 3 points, on peut créer les 14 zones de l’image tout en conservant la zone ‘Restricted Area’ et ‘Backcourt’.
train$shot_zone_detailed <- NA
train$shot_zone_detailed[train$loc_x <= -220 & train$loc_y <= 100 & train$shot_type == "3PT Field Goal"] = "A"
train$shot_zone_detailed[train$loc_x >= -220 & train$loc_x <= -150 & train$loc_y <= 100 & train$shot_type == "2PT Field Goal"] = "B"
train$shot_zone_detailed[train$loc_x < -90 & train$shot_type == "3PT Field Goal" & train$loc_y > 100] = "C"
train$shot_zone_detailed[train$loc_x < 90 & train$loc_x > -90 & train$shot_type == "3PT Field Goal"] = "D"
train$shot_zone_detailed[train$loc_x < 70 & train$loc_x > -70 & train$loc_y > 150 & train$shot_type == "2PT Field Goal"] = "F"
train$shot_zone_detailed[train$loc_x > 70 & train$shot_type == "3PT Field Goal"] = "G"
train$shot_zone_detailed[train$loc_x < 90 & train$loc_x > -90 & train$loc_y > 90 & train$loc_y < 150 & train$shot_type == "2PT Field Goal"] = "J"
train$shot_zone_detailed[sqrt(train$loc_x^2 + train$loc_y^2) < 90] = "L"
train$shot_zone_detailed[train$loc_x < 220 & train$loc_x > 150 & train$loc_y <= 100] = "M"
train$shot_zone_detailed[train$loc_x > 220 & train$loc_y < 100 & train$shot_type == "3PT Field Goal"] = "N"
train$shot_zone_detailed[is.na(train$shot_zone_detailed) & train$loc_y > 100 & train$loc_x > -210 & train$loc_x < 70] = "E"
train$shot_zone_detailed[is.na(train$shot_zone_detailed) & train$loc_y > 100 & train$loc_x >= 70 & train$loc_x < 210] = "H"
train$shot_zone_detailed[is.na(train$shot_zone_detailed) & train$loc_y <= 100 & train$loc_x < 0] = "I"
train$shot_zone_detailed[is.na(train$shot_zone_detailed) & train$loc_y <= 100 & train$loc_x > 0] = "K"
train$shot_zone_detailed[train$shot_zone_basic == 'Restricted Area'] = "Restricted Area"
train$shot_zone_detailed[train$shot_zone_basic == 'Backcourt'] = "Backcourt"
On peut ensuite visualiser cette variable
courtplot(shot_zone_detailed)
## Warning: Removed 105 rows containing missing values (geom_point).
Super! Pour le fun, on peut s’intéresser au pourcentage de réussite d’un joueur professionnel sur toute sa carrière. On commence par calculer la moyenne des coordonnées des positions par zone pour pouvoir représenter nos chiffres :
mean_x = aggregate(train$loc_x, list(train$shot_zone_detailed), na.rm = TRUE, mean)
mean_y = aggregate(train$loc_y, list(train$shot_zone_detailed), na.rm = TRUE, mean)
mean_xy = data.frame(mean_x$Group.1, mean_x$x, mean_y$x)
On calcule ensuite le pourcentage de réussite par zone, ainsi que le nombre de tirs par zone :
pourcentage_shot = as.data.frame(prop.table(table(train$shot_made_flag, train$shot_zone_detailed), 2))
shot_made_by_zone = as.data.frame(table(train$shot_made_flag, train$shot_zone_detailed))
On peut ensuite représenter nos pourcentages :
courtimg = readPNG('court.png')
plot(1, type="n", xlab="", ylab="", xlim=c(-235, 235), ylim=c(-30, 400))
lim <- par()
rasterImage(courtimg, lim$usr[1], lim$usr[3], lim$usr[2], lim$usr[4])
grid()
for (zone in c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "Restricted Area", "Backcourt")) {
x = mean_xy[mean_xy$mean_x.Group.1 == zone,]$mean_x.x
y = mean_xy[mean_xy$mean_x.Group.1 == zone,]$mean_y.x
text_pourcentage = pourcentage_shot[pourcentage_shot$Var1 == 1 & pourcentage_shot$Var2 == zone,]$Freq
success_shot = shot_made_by_zone[shot_made_by_zone$Var2 == zone & shot_made_by_zone$Var1 == 1,]$Freq
missed_shot = shot_made_by_zone[shot_made_by_zone$Var2 == zone & shot_made_by_zone$Var1 == 0,]$Freq
if (zone == "Backcourt") {
text(0, 390, sprintf("%d%% \n %d / %d", round(text_pourcentage * 100), success_shot, success_shot + missed_shot))
} else {
text(x, y, sprintf("%d%% \n %d / %d", round(text_pourcentage * 100), success_shot, success_shot + missed_shot))
}
}
Black Mamba était-il un shoteur à 3 points comme Stefen Curry ? Au vue des pourcentages de shot précédents, on peut rapidement estimer un pourcentage de 30% de réussite à 3 pts. Vérifions-ça :
prop.table(table(train$shot_made_flag, train$shot_type))
##
## 2PT Field Goal 3PT Field Goal
## 0 0.41257734 0.14126163
## 1 0.37681441 0.06934662
prop.table(table(train$shot_made_flag, train$shot_type), 2)
##
## 2PT Field Goal 3PT Field Goal
## 0 0.5226522 0.6707317
## 1 0.4773478 0.3292683
Pour approximativement 21% de shots pris à 3 points, 33% de réussite! Pour les tirs à 2pts, 52% de manqués contre 47% de succès. Le tir à 2 pts ne nous aidera pas à déterminer la réussite du tir.
Intéressons nous maintenant à la distance de tir. Nous disposons de deux variables pour ce faire : shot_zone_range, shot_distance.
courtplot(shot_zone_range)
## Warning: Removed 105 rows containing missing values (geom_point).
ggplot(data = train, aes_q(x = train$shot_distance)) +
geom_bar(aes(fill = shot_made_flag), stat = "count", position = "fill")
On voit clairement que plus le joueur est proche du panier et plus sa réussite est importante.
On dispose de deux variables pour représenter le type d’action : ‘action_type’ et ‘combined_shot_type’
head(train[,c("action_type", "combined_shot_type")])
## action_type combined_shot_type
## 2 Jump Shot Jump Shot
## 3 Jump Shot Jump Shot
## 4 Jump Shot Jump Shot
## 5 Driving Dunk Shot Dunk
## 6 Jump Shot Jump Shot
## 7 Layup Shot Layup
summary(train$action_type)
## Alley Oop Dunk Shot Alley Oop Layup shot
## 95 67
## Cutting Finger Roll Layup Shot Cutting Layup Shot
## 0 6
## Driving Bank shot Driving Dunk Shot
## 3 257
## Driving Finger Roll Layup Shot Driving Finger Roll Shot
## 59 68
## Driving Floating Bank Jump Shot Driving Floating Jump Shot
## 1 3
## Driving Hook Shot Driving Jump shot
## 13 23
## Driving Layup Shot Driving Reverse Layup Shot
## 1628 83
## Driving Slam Dunk Shot Dunk Shot
## 43 217
## Fadeaway Bank shot Fadeaway Jump Shot
## 27 872
## Finger Roll Layup Shot Finger Roll Shot
## 28 26
## Floating Jump shot Follow Up Dunk Shot
## 93 10
## Hook Bank Shot Hook Shot
## 5 73
## Jump Bank Shot Jump Hook Shot
## 289 19
## Jump Shot Layup Shot
## 15836 2154
## Pullup Bank shot Pullup Jump shot
## 11 402
## Putback Dunk Shot Putback Layup Shot
## 3 9
## Putback Slam Dunk Shot Reverse Dunk Shot
## 2 61
## Reverse Layup Shot Reverse Slam Dunk Shot
## 333 15
## Running Bank shot Running Dunk Shot
## 43 18
## Running Finger Roll Layup Shot Running Finger Roll Shot
## 5 4
## Running Hook Shot Running Jump Shot
## 33 779
## Running Layup Shot Running Pull-Up Jump Shot
## 51 3
## Running Reverse Layup Shot Running Slam Dunk Shot
## 7 1
## Running Tip Shot Slam Dunk Shot
## 1 334
## Step Back Jump shot Tip Layup Shot
## 106 2
## Tip Shot Turnaround Bank shot
## 151 58
## Turnaround Fadeaway Bank Jump Shot Turnaround Fadeaway shot
## 0 366
## Turnaround Finger Roll Shot Turnaround Hook Shot
## 2 8
## Turnaround Jump Shot
## 891
Beaucoup d’actions sont répertoriées! Certaines n’ont même jamais été réalisé. Par exemple le ‘Turnaround Fadeaway Bank Jump Shot’ ou le ‘Cutting Finger Roll Layup Shot’.
summary(train$combined_shot_type)
## Bank Shot Dunk Hook Shot Jump Shot Layup Tip Shot
## 120 1056 127 19710 4532 152
Il existe beaucoup moins de type d’actions combinées. Kobe a réalisé peu de Hook Shot, c’est à dire un “bras roulé” ni même de Tip Shot qui sont des claquettes en français. On peut représenter le pourcentage de réussite suivant le type d’action :
pourcentage_par_action = prop.table(table(train$action_type, train$shot_made_flag),1)
pourcentage_par_action = as.data.frame.matrix(pourcentage_par_action)
pourcentage_par_action = pourcentage_par_action[order(pourcentage_par_action[,"1"]),]
pourcentage_par_action$action_type = rownames(pourcentage_par_action)
pourcentage_par_action$evaluation[pourcentage_par_action[, "1"] >= 0.9] = "90% >="
pourcentage_par_action$evaluation[pourcentage_par_action[, "1"] < 0.9] = "< 90%"
ggplot(data = pourcentage_par_action, aes(x = reorder(action_type, `1`))) +
coord_flip() +
geom_bar(aes(y = `1`, fill= evaluation), stat = "identity") +
scale_fill_manual(values = c("< 90%" = "darkblue", "90% >=" = "red"))
## Warning: Removed 2 rows containing missing values (position_stack).
Les actions rouges représentent celles pour lesquelles il y a plus de 90% de réussite. On réitère l’opération avec les actions combinées.
pourcentage_par_action = prop.table(table(train$combined_shot_type, train$shot_made_flag),1)
pourcentage_par_action = as.data.frame.matrix(pourcentage_par_action)
pourcentage_par_action = pourcentage_par_action[order(pourcentage_par_action[,"1"]),]
pourcentage_par_action$action_type = rownames(pourcentage_par_action)
pourcentage_par_action$evaluation[pourcentage_par_action[, "1"] >= 0.9] = "90% >="
pourcentage_par_action$evaluation[pourcentage_par_action[, "1"] < 0.9] = "< 90%"
ggplot(data = pourcentage_par_action, aes(x = reorder(action_type, `1`))) +
coord_flip() +
geom_bar(aes(y = `1`, fill= evaluation), stat = "identity") +
scale_fill_manual(values = c("< 90%" = "darkblue", "90% >=" = "red"))
En tant que joueur, je sais très bien que l’angle de tir est important. J’utilise particulièrement les shots à 45° avec la planche. On ne dispose malheureusement pas de la notion d’angle de tir dans notre jeu de données mais il est possible de le calculer. En effet, nous disposons des coordonnées cartésiennnes des tirs où l’origine de notre axe n’est autre que le panier.
On calcule un angle de tir allant de 0 à 360°. En effet, étant donné que notre origine est l’anneau, des tirs ont pu avoir lieu derrière le panier.
train$angle <- NA
train$angle = apply(train[,c('loc_x', 'loc_y')], 1, function(vector) {
atan2(abs(vector[2]), abs(vector[1])) * 180 / pi
})
train$angle[train$loc_x < 0 & train$loc_y < 0] = 360 - train$angle[train$loc_x < 0 & train$loc_y < 0]
train$angle[train$loc_x > 0 & train$loc_y > 0] = 180 - train$angle[train$loc_x > 0 & train$loc_y > 0]
train$angle[train$loc_x > 0 & train$loc_y < 0] = 180 + train$angle[train$loc_x > 0 & train$loc_y < 0]
summary(train$angle)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.00 25.30 82.55 84.53 130.40 359.80
train$grouped_angle = cut(train$angle, breaks = seq(0, 360, 5), include.lowest=TRUE)
summary(train$grouped_angle)
## [0,5] (5,10] (10,15] (15,20] (20,25] (25,30] (30,35]
## 5069 334 318 339 344 398 447
## (35,40] (40,45] (45,50] (50,55] (55,60] (60,65] (65,70]
## 530 648 749 778 734 591 472
## (70,75] (75,80] (80,85] (85,90] (90,95] (95,100] (100,105]
## 467 432 390 870 705 470 429
## (105,110] (110,115] (115,120] (120,125] (125,130] (130,135] (135,140]
## 504 666 808 869 847 825 653
## (140,145] (145,150] (150,155] (155,160] (160,165] (165,170] (170,175]
## 595 530 429 363 360 419 479
## (175,180] (180,185] (185,190] (190,195] (195,200] (200,205] (205,210]
## 554 466 171 52 20 11 9
## (210,215] (215,220] (220,225] (225,230] (230,235] (235,240] (240,245]
## 3 5 4 2 1 3 0
## (245,250] (250,255] (255,260] (260,265] (265,270] (270,275] (275,280]
## 1 2 5 3 0 0 2
## (280,285] (285,290] (290,295] (295,300] (300,305] (305,310] (310,315]
## 1 3 3 2 2 1 4
## (315,320] (320,325] (325,330] (330,335] (335,340] (340,345] (345,350]
## 3 1 5 2 9 11 51
## (350,355] (355,360]
## 142 282
courtplot(train$grouped_angle)
## Warning: Removed 105 rows containing missing values (geom_point).
pourcentage_par_angle = prop.table(table(train$grouped_angle, train$shot_made_flag),1)
pourcentage_par_angle = as.data.frame.matrix(pourcentage_par_angle)
pourcentage_par_angle = pourcentage_par_angle[order(pourcentage_par_angle[,"1"]),]
pourcentage_par_angle$grouped_angle = rownames(pourcentage_par_angle)
pourcentage_par_angle$evaluation[pourcentage_par_angle[, "1"] >= 0.9] = "90% >="
pourcentage_par_angle$evaluation[pourcentage_par_angle[, "1"] < 0.9] = "< 90%"
ggplot(data = pourcentage_par_angle, aes(x = reorder(grouped_angle, `1`))) +
coord_flip() +
geom_bar(aes(y = `1`, fill= evaluation), stat = "identity") +
scale_fill_manual(values = c("< 90%" = "darkblue", "90% >=" = "red"))
## Warning: Removed 3 rows containing missing values (position_stack).
Inspectons les shots qui ont un taux de réussite supérieur à 90%, c’est à dire pour un angle compris entre 225 et 325.
temporary_train = train[train$angle ==135,]
nrow(temporary_train)
## [1] 76
Ici, on calcule un angle que je nomme ‘absolue’, c’est à dire que l’on aura le même angle à gauche ou à droite du panier.
train$absolute_angle <- NA
train$absolute_angle = apply(train[,c('loc_x', 'loc_y')], 1, function(vector) {
atan2(abs(vector[2]), abs(vector[1])) * 180 / pi
})
summary(train$absolute_angle)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.000 5.194 40.380 37.760 61.390 90.000
train$grouped_absolute_angle = cut(train$absolute_angle, breaks = seq(0, 90, 5), include.lowest=TRUE)
summary(train$grouped_absolute_angle)
## [0,5] (5,10] (10,15] (15,20] (20,25] (25,30] (30,35] (35,40] (40,45]
## 6371 1126 840 730 727 838 985 1131 1388
## (45,50] (50,55] (55,60] (60,65] (65,70] (70,75] (75,80] (80,85] (85,90]
## 1500 1627 1608 1401 1142 976 867 865 1575
courtplot(train$grouped_absolute_angle)
## Warning: Removed 105 rows containing missing values (geom_point).
Cool! On a un jolie arc-en-ciel. Calculons maintenant les pourcentages de réussite aux tirs.
pourcentage_par_angle = prop.table(table(train$grouped_absolute_angle, train$shot_made_flag),1)
pourcentage_par_angle = as.data.frame.matrix(pourcentage_par_angle)
pourcentage_par_angle = pourcentage_par_angle[order(pourcentage_par_angle[,"1"]),]
pourcentage_par_angle$grouped_angle = rownames(pourcentage_par_angle)
pourcentage_par_angle$evaluation[pourcentage_par_angle[, "1"] >= 0.9] = "90% >="
pourcentage_par_angle$evaluation[pourcentage_par_angle[, "1"] < 0.9] = "< 90%"
ggplot(data = pourcentage_par_angle, aes(x = reorder(grouped_angle, `1`))) +
coord_flip() +
geom_bar(aes(y = `1`, fill= evaluation), stat = "identity") +
scale_fill_manual(values = c("< 90%" = "darkblue", "90% >=" = "red"))